package com.fsh.subject.infra.basic.service.impl;

import com.fsh.subject.common.entity.PageResult;
import com.fsh.subject.common.enums.SubjectInfoTypeEnum;
import com.fsh.subject.infra.basic.entity.EsSubjectFields;
import com.fsh.subject.infra.basic.entity.SubjectInfoEs;
import com.fsh.subject.infra.basic.es.EsIndexInfo;
import com.fsh.subject.infra.basic.es.EsRestClient;
import com.fsh.subject.infra.basic.es.EsSearchRequest;
import com.fsh.subject.infra.basic.es.EsSourceData;
import com.fsh.subject.infra.basic.service.SubjectEsService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.MapUtils;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.MatchQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;

@Service
@Slf4j
public class SubjectEsServiceImpl implements SubjectEsService {
    /**
     * 插入题目文档
     *
     * @param subjectInfoEs
     */
    @Override
    public boolean insert(SubjectInfoEs subjectInfoEs) {
        // 构建EsSourceData对象
        EsSourceData esSourceData=new EsSourceData();
        // 转换成map
        Map<String, Object> data = convert2EsSourceData(subjectInfoEs);
        //填充EsSourceData
        esSourceData.setDocId(subjectInfoEs.getDocId().toString());
        esSourceData.setData(data);
        //插入文档
        return EsRestClient.insertDoc(getEsIndexInfo(),esSourceData);
    }

    /**
     * 私有方法，将SubjectInfoEs里的数据，转成map数据
     */
    private Map<String, Object> convert2EsSourceData(SubjectInfoEs subjectInfoEs) {
        Map<String, Object> data = new HashMap<>();
        data.put(EsSubjectFields.SUBJECT_ID, subjectInfoEs.getSubjectId());
        data.put(EsSubjectFields.DOC_ID, subjectInfoEs.getDocId());
        data.put(EsSubjectFields.SUBJECT_NAME, subjectInfoEs.getSubjectName());
        data.put(EsSubjectFields.SUBJECT_ANSWER, subjectInfoEs.getSubjectAnswer());
        data.put(EsSubjectFields.SUBJECT_TYPE, subjectInfoEs.getSubjectType());
        data.put(EsSubjectFields.CREATE_USER, subjectInfoEs.getCreateUser());
        data.put(EsSubjectFields.CREATE_TIME, subjectInfoEs.getCreateTime());
        return data;
    }


    /**
     * 返回一个PageResult对象，该对象包含SubjectInfoEs列表和分页信息
     * 就是 关键字搜索 ，返回命中列表 ，并高亮
     */
    @Override
    public PageResult<SubjectInfoEs> querySubjectList(SubjectInfoEs req) {
        // 创建一个新的PageResult对象，用于存储分页结果
        PageResult<SubjectInfoEs> pageResult = new PageResult<>();

        // 使用req参数调用createSearchListQuery方法创建EsSearchRequest对象
        EsSearchRequest esSearchRequest = createSearchListQuery(req);

        // 调用EsRestClient的searchWithTermQuery方法，
        // 传入EsIndexInfo和esSearchRequest对象，执行Elasticsearch搜索操作
        SearchResponse searchResponse = EsRestClient.
                searchWithTermQuery(getEsIndexInfo(), esSearchRequest);

        // 创建一个新的LinkedList对象，用于存储搜索结果中的SubjectInfoEs对象
        List<SubjectInfoEs> subjectInfoEsList = new ArrayList<>();

        // 获取搜索响应中的搜索命中结果
        SearchHits searchHits = searchResponse.getHits();

        // 如果搜索命中结果为空，则设置pageResult的属性并返回
        if (searchHits == null || searchHits.getHits() == null) {
            pageResult.setPageNo(req.getPageNo()); // 设置分页的页码
            pageResult.setPageSize(req.getPageSize()); // 设置每页的数量
            pageResult.setRecords(subjectInfoEsList); // 设置记录列表
            pageResult.setTotal(0); // 设置总记录数为0
            return pageResult; // 返回pageResult对象
        }

        // 获取搜索命中结果的数组
        SearchHit[] hits = searchHits.getHits();

        // 遍历搜索命中结果数组，将每个命中结果转换为SubjectInfoEs对象，并添加到subjectInfoEsList中
        for (SearchHit hit : hits) {
            SubjectInfoEs subjectInfoEs = convertResult(hit); // 将命中结果转换为SubjectInfoEs对象
            if (Objects.nonNull(subjectInfoEs)) { // 如果转换后的对象不为空，则添加到列表中
                subjectInfoEsList.add(subjectInfoEs);
            }
        }

        // 设置pageResult的属性并返回
        pageResult.setPageNo(req.getPageNo()); // 设置分页的页码
        pageResult.setPageSize(req.getPageSize()); // 设置每页的数量
        pageResult.setRecords(subjectInfoEsList); // 设置记录列表
        // 设置总记录数，将long类型转换为int类型后设置
        pageResult.setTotal(Long.valueOf(searchHits.getTotalHits().value).intValue());
        return pageResult; // 返回pageResult对象
    }

    /**
     * 定义一个私有方法，将SearchHit结果转换为SubjectInfoEs对象，并附带高亮。
     */
    private SubjectInfoEs convertResult(SearchHit hit) {
        // 将SearchHit的source转化为Map类型
        Map<String, Object> sourceAsMap = hit.getSourceAsMap();

        // 如果sourceAsMap为空或者没有内容，则直接返回null
        if (CollectionUtils.isEmpty(sourceAsMap)) {
            return null;
        }

        // 创建一个新的SubjectInfoEs对象
        SubjectInfoEs result = new SubjectInfoEs();

        // 从sourceAsMap中获取subjectId的值，并设置到result对象中
        result.setSubjectId(MapUtils.getLong(sourceAsMap, EsSubjectFields.SUBJECT_ID));

        // 从sourceAsMap中获取subjectName的值，并设置到result对象中
        result.setSubjectName(MapUtils.getString(sourceAsMap, EsSubjectFields.SUBJECT_NAME));

        // 从sourceAsMap中获取subjectAnswer的值，并设置到result对象中
        result.setSubjectAnswer(MapUtils.getString(sourceAsMap, EsSubjectFields.SUBJECT_ANSWER));

        // 从sourceAsMap中获取docId的值，并设置到result对象中
        result.setDocId(MapUtils.getLong(sourceAsMap, EsSubjectFields.DOC_ID));

        // 从sourceAsMap中获取subjectType的值，并设置到result对象中
        result.setSubjectType(MapUtils.getInteger(sourceAsMap, EsSubjectFields.SUBJECT_TYPE));

        // 将hit对象的score值转换为BigDecimal类型，并乘以100.00后设置到result对象中
        result.setScore(new BigDecimal(String.valueOf(hit.getScore())).multiply(new BigDecimal("100.00")
                .setScale(2, RoundingMode.HALF_UP)));

        // 处理name的高亮部分，如果存在高亮字段，则将高亮后的文本拼接起来并设置到result对象中
        // 注释：这部分代码处理的是subjectName的高亮显示，如果搜索结果中有高亮的词，那么这些词会被高亮显示出来。
        // 注意：在实际应用中，这段代码可能需要更多的错误处理和边界检查。
        // 例如，如果HighlightField是null或者没有fragments，这里都没有做处理。

        // 从hit中获取所有高亮字段
        Map<String, HighlightField> highlightFields = hit.getHighlightFields();
        // 从高亮字段中获取名为"subject_name"的字段
        HighlightField subjectNameField = highlightFields.get(EsSubjectFields.SUBJECT_NAME);
        // 检查subjectNameField是否非空
        if(Objects.nonNull(subjectNameField)){
            // 获取subjectNameField中的所有片段
            Text[] fragments = subjectNameField.getFragments();
            // 创建一个StringBuilder对象，用于拼接片段内容
            StringBuilder subjectNameBuilder = new StringBuilder();
            // 遍历所有片段，并将它们添加到subjectNameBuilder中
            for (Text fragment : fragments) {
                subjectNameBuilder.append(fragment);
            }
            // 将拼接后的subjectNameBuilder内容设置为result的subjectName属性
            result.setSubjectName(subjectNameBuilder.toString());
        }

        // 处理答案高亮部分，如果存在高亮字段，则将高亮后的文本拼接起来并设置到result对象中
        // 注释：这部分代码处理的是subjectAnswer的高亮显示，如果搜索结果中有高亮的词，那么这些词会被高亮显示出来。
        // 注意：在实际应用中，这段代码可能需要更多的错误处理和边界检查。
        // 例如，如果HighlightField是null或者没有fragments，这里都没有做处理。
        HighlightField subjectAnswerField = highlightFields.get(EsSubjectFields.SUBJECT_ANSWER);
        if(Objects.nonNull(subjectAnswerField)){
            Text[] fragments = subjectAnswerField.getFragments();
            StringBuilder subjectAnswerBuilder = new StringBuilder();
            for (Text fragment : fragments) {
                subjectAnswerBuilder.append(fragment);
            }
            result.setSubjectAnswer(subjectAnswerBuilder.toString());
        }

        // 返回处理后的result对象
        return result;
    }

    // 定义一个方法，根据传入的SubjectInfoEs对象创建并返回一个EsSearchRequest对象，用于在Elasticsearch中执行搜索操作
    private EsSearchRequest createSearchListQuery(SubjectInfoEs req) {
        // 创建一个新的EsSearchRequest对象
        EsSearchRequest esSearchRequest = new EsSearchRequest();

        // 创建一个BoolQueryBuilder对象，用于构建复杂的搜索查询条件
        BoolQueryBuilder bq = new BoolQueryBuilder();

        // 创建一个MatchQueryBuilder对象，用于构建匹配查询条件
        MatchQueryBuilder subjectNameQueryBuilder =
                QueryBuilders.matchQuery(EsSubjectFields.SUBJECT_NAME, req.getKeyWord());

        // 将subjectNameQueryBuilder添加到bq中，表示这个查询条件是可选的（should）
        bq.should(subjectNameQueryBuilder);

        // 设置subjectNameQueryBuilder的权重为2，表示这个查询条件相对重要
        subjectNameQueryBuilder.boost(2);

        // 创建另一个MatchQueryBuilder对象，用于构建另一个匹配查询条件
        MatchQueryBuilder subjectAnswerQueryBuilder =
                QueryBuilders.matchQuery(EsSubjectFields.SUBJECT_ANSWER, req.getKeyWord());

        // 将subjectAnswerQueryBuilder添加到bq中，表示这个查询条件也是可选的（should）
        bq.should(subjectAnswerQueryBuilder);

        // todo 这里可以修改，不一定就必须是 单选题，数字1 .
        // 注意，这里 type 是数字 ，getCode的是值是 数字 ，es里面存的type也是数字
        // 创建另一个MatchQueryBuilder对象，用于构建另一个匹配查询条件，这个查询条件是必须的（must）
        MatchQueryBuilder subjectTypeQueryBuilder =
                QueryBuilders.matchQuery(EsSubjectFields.SUBJECT_TYPE, SubjectInfoTypeEnum.BRIEF.getCode());

        // 将subjectTypeQueryBuilder添加到bq中，表示这个查询条件是必须的（must）
        bq.must(subjectTypeQueryBuilder);

        // 设置minimumShouldMatch为1，表示至少有一个可选的查询条件需要匹配上
        bq.minimumShouldMatch(1);

        // 创建一个HighlightBuilder对象，用于构建高亮显示文本的查询条件
        HighlightBuilder highlightBuilder = new HighlightBuilder().field("*").requireFieldMatch(false);
        highlightBuilder.preTags("<span style = \"color:red\">"); // 设置高亮文本的前缀标签样式为红色
        highlightBuilder.postTags("</span>"); // 设置高亮文本的后缀标签样式为红色结束标签

        // 设置esSearchRequest的查询条件和高亮显示设置
        esSearchRequest.setBq(bq);
        esSearchRequest.setHighlightBuilder(highlightBuilder);
        esSearchRequest.setFields(EsSubjectFields.FIELD_QUERY); // 设置需要返回的字段
        esSearchRequest.setFrom((req.getPageNo() - 1) * req.getPageSize()); // 设置分页起始位置
        esSearchRequest.setSize(req.getPageSize()); // 设置每页显示的数量
        esSearchRequest.setNeedScroll(false); // 设置是否需要滚动分页，这里设置为不需要滚动分页
        return esSearchRequest; // 返回构建好的EsSearchRequest对象
    }

    /**
     * 私有方法，获取es的索引信息
     */
    private EsIndexInfo getEsIndexInfo() {
        //构建索引信息
        EsIndexInfo esIndexInfo = new EsIndexInfo();
        //索引名
        esIndexInfo.setIndexName("subject_index");
        //集群名称
        esIndexInfo.setClusterName("791bb269e4be");

        return esIndexInfo;
    }
}
